package com.nulldev.util.io.extensions.nstream.impl;

import java.io.IOException;

import com.nulldev.util.JVM.JVM;
import com.nulldev.util.VariableAPI.ByteUtil;
import com.nulldev.util.data.longNumbers.lNumber;
import com.nulldev.util.io.extensions.nstream.nStream;

public class ByteArrayStream implements nStream {

	private boolean readable, writeable;
	private volatile byte[] data;
	private volatile lNumber position = lNumber.nil();

	public ByteArrayStream(final byte[] data) {
		this.data = data;
		this.readable = true;
	}

	public ByteArrayStream(final int allocated) {
		this.data = new byte[0];
		this.writeable = true;
	}

	public ByteArrayStream(final byte[] data, final boolean writeable) {
		this.data = data;
		this.readable = true;
		this.writeable = writeable;
	}

	@Override
	@Deprecated
	public void sync() throws IOException {
	}

	@Override
	public void close() throws IOException {
		this.data = null;
		System.gc();
	}

	@Override
	public void reset() throws IOException, UnsupportedOperationException {
		this.position.setSelf(0L);
	}

	@Override
	public int positionLegacy() {
		return this.position.intValue();
	}

	@Override
	public lNumber position() {
		return this.position;
	}

	@Override
	public byte[] next(final int bytes) throws IOException {
		if (!this.readable()) {
			throw new IOException("Cannot read array!");
		} else if (this.data == null || this.data.length == 0) {
			throw new IOException("Stream already closed!");
		} else if (this.position.add(bytes).moreThan(this.data.length)) {
			throw new IOException("You have read the full array!");
		}
		byte[] alloc = new byte[bytes];
		for (int i = 0; i < bytes; i++) {
			alloc[i] = (byte) next();
		}
		return alloc;
	}

	@Override
	public lNumber available() throws UnsupportedOperationException {
		return lNumber.fromInt(this.data.length);
	}

	@Override
	public int next() throws IOException {
		if (!this.readable()) {
			throw new IOException("Cannot read array!");
		} else if (this.data == null || this.data.length == 0) {
			throw new IOException("Stream already closed!");
		} else if (this.position.add(1).moreThan(this.data.length)) {
			throw new IOException("You have read the full array!");
		}
		final byte b = this.data[this.position.intValue()];
		this.position.addSelf(1);
		return b;
	}

	@Override
	public int at(int position) throws IOException {
		if (!this.readable()) {
			throw new IOException("Cannot read array!");
		} else if (this.data == null) {
			throw new IOException("Stream already closed!");
		}
		return this.data[position];
	}

	@Override
	public boolean readable() {
		return this.readable;
	}

	@Override
	public boolean writeable() {
		return this.writeable;
	}

	@Override
	public void write(int _byte) throws IOException {
		if (!this.writeable()) {
			throw new IOException("Cannot write data into array!");
		} else if (this.data == null) {
			throw new IOException("Stream already closed!");
		} else if (data == null || data.length == 0) {
			throw new IOException("Cannot write nothing or null!");
		}
		this.data[position.intValue()] = (byte) _byte;
		this.position.addSelf(1);
	}

	@Override
	public void write(final byte[] data) throws IOException {
		if (!this.writeable()) {
			throw new IOException("Cannot write data into array!");
		} else if (this.data == null) {
			throw new IOException("Stream already closed!");
		} else if (data == null || data.length == 0) {
			throw new IOException("Cannot write nothing or null!");
		}
		for (final byte d : data) {
			this.data[this.position.intValue()] = d;
			this.position.addSelf(1L);
		}
	}

	public static void main(String[] args) throws IOException {
		// demo
		final ByteArrayStream bs = new ByteArrayStream(ByteUtil.create(512), true);
		bs.write("Hello world!".getBytes());
		bs.reset();
		final byte[] output = bs.next(512);
		JVM.println(new String(output));
	}

}